package spnego

import (
	
	
	

	
	
	
	
	
	
)

// https://msdn.microsoft.com/en-us/library/ms995330.aspx

// Negotiation state values.
const (
	NegStateAcceptCompleted  NegState = 0
	NegStateAcceptIncomplete NegState = 1
	NegStateReject           NegState = 2
	NegStateRequestMIC       NegState = 3
)

// NegState is a type to indicate the SPNEGO negotiation state.
type NegState int

// NegTokenInit implements Negotiation Token of type Init.
type NegTokenInit struct {
	MechTypes      []asn1.ObjectIdentifier
	ReqFlags       asn1.BitString
	MechTokenBytes []byte
	MechListMIC    []byte
	mechToken      gssapi.ContextToken
	settings       *service.Settings
}

type marshalNegTokenInit struct {
	MechTypes      []asn1.ObjectIdentifier `asn1:"explicit,tag:0"`
	ReqFlags       asn1.BitString          `asn1:"explicit,optional,tag:1"`
	MechTokenBytes []byte                  `asn1:"explicit,optional,omitempty,tag:2"`
	MechListMIC    []byte                  `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens
}

// NegTokenResp implements Negotiation Token of type Resp/Targ
type NegTokenResp struct {
	NegState      asn1.Enumerated
	SupportedMech asn1.ObjectIdentifier
	ResponseToken []byte
	MechListMIC   []byte
	mechToken     gssapi.ContextToken
	settings      *service.Settings
}

type marshalNegTokenResp struct {
	NegState      asn1.Enumerated       `asn1:"explicit,tag:0"`
	SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,tag:1"`
	ResponseToken []byte                `asn1:"explicit,optional,omitempty,tag:2"`
	MechListMIC   []byte                `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens
}

// NegTokenTarg implements Negotiation Token of type Resp/Targ
type NegTokenTarg NegTokenResp

// Marshal an Init negotiation token
func ( *NegTokenInit) () ([]byte, error) {
	 := marshalNegTokenInit{
		MechTypes:      .MechTypes,
		ReqFlags:       .ReqFlags,
		MechTokenBytes: .MechTokenBytes,
		MechListMIC:    .MechListMIC,
	}
	,  := asn1.Marshal()
	if  != nil {
		return nil, 
	}
	 := asn1.RawValue{
		Tag:        0,
		Class:      2,
		IsCompound: true,
		Bytes:      ,
	}
	,  := asn1.Marshal()
	if  != nil {
		return nil, 
	}
	return , nil
}

// Unmarshal an Init negotiation token
func ( *NegTokenInit) ( []byte) error {
	, ,  := UnmarshalNegToken()
	if  != nil {
		return 
	}
	if ! {
		return errors.New("bytes were not that of a NegTokenInit")
	}
	 := .(NegTokenInit)
	.MechTokenBytes = .MechTokenBytes
	.MechListMIC = .MechListMIC
	.MechTypes = .MechTypes
	.ReqFlags = .ReqFlags
	return nil
}

// Verify an Init negotiation token
func ( *NegTokenInit) () (bool, gssapi.Status) {
	// Check if supported mechanisms are in the MechTypeList
	var  bool
	for ,  := range .MechTypes {
		if .Equal(gssapi.OIDKRB5.OID()) || .Equal(gssapi.OIDMSLegacyKRB5.OID()) {
			if .mechToken == nil && .MechTokenBytes == nil {
				return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
			}
			 = true
			break
		}
	}
	if ! {
		return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"}
	}
	// There should be some mechtoken bytes for a KRB5Token (other mech types are not supported)
	 := new(KRB5Token)
	.settings = .settings
	if .mechToken == nil {
		 := .Unmarshal(.MechTokenBytes)
		if  != nil {
			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: .Error()}
		}
		.mechToken = 
	} else {
		var  bool
		,  = .mechToken.(*KRB5Token)
		if ! {
			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"}
		}
	}
	// Verify the mechtoken
	return .mechToken.Verify()
}

// Context returns the SPNEGO context which will contain any verify user identity information.
func ( *NegTokenInit) () context.Context {
	if .mechToken != nil {
		,  := .mechToken.(*KRB5Token)
		if ! {
			return nil
		}
		return .Context()
	}
	return nil
}

// Marshal a Resp/Targ negotiation token
func ( *NegTokenResp) () ([]byte, error) {
	 := marshalNegTokenResp{
		NegState:      .NegState,
		SupportedMech: .SupportedMech,
		ResponseToken: .ResponseToken,
		MechListMIC:   .MechListMIC,
	}
	,  := asn1.Marshal()
	if  != nil {
		return nil, 
	}
	 := asn1.RawValue{
		Tag:        1,
		Class:      2,
		IsCompound: true,
		Bytes:      ,
	}
	,  := asn1.Marshal()
	if  != nil {
		return nil, 
	}
	return , nil
}

// Unmarshal a Resp/Targ negotiation token
func ( *NegTokenResp) ( []byte) error {
	, ,  := UnmarshalNegToken()
	if  != nil {
		return 
	}
	if  {
		return errors.New("bytes were not that of a NegTokenResp")
	}
	 := .(NegTokenResp)
	.MechListMIC = .MechListMIC
	.NegState = .NegState
	.ResponseToken = .ResponseToken
	.SupportedMech = .SupportedMech
	return nil
}

// Verify a Resp/Targ negotiation token
func ( *NegTokenResp) () (bool, gssapi.Status) {
	if .SupportedMech.Equal(gssapi.OIDKRB5.OID()) || .SupportedMech.Equal(gssapi.OIDMSLegacyKRB5.OID()) {
		if .mechToken == nil && .ResponseToken == nil {
			return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
		}
		 := new(KRB5Token)
		.settings = .settings
		if .mechToken == nil {
			 := .Unmarshal(.ResponseToken)
			if  != nil {
				return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: .Error()}
			}
			.mechToken = 
		} else {
			var  bool
			,  = .mechToken.(*KRB5Token)
			if ! {
				return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"}
			}
		}
		if  == nil {
			return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
		}
		// Verify the mechtoken
		return .Verify()
	}
	return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"}
}

// State returns the negotiation state of the negotiation response.
func ( *NegTokenResp) () NegState {
	return NegState(.NegState)
}

// Context returns the SPNEGO context which will contain any verify user identity information.
func ( *NegTokenResp) () context.Context {
	if .mechToken != nil {
		,  := .mechToken.(*KRB5Token)
		if ! {
			return nil
		}
		return .Context()
	}
	return nil
}

// UnmarshalNegToken umarshals and returns either a NegTokenInit or a NegTokenResp.
//
// The boolean indicates if the response is a NegTokenInit.
// If error is nil and the boolean is false the response is a NegTokenResp.
func ( []byte) (bool, interface{}, error) {
	var  asn1.RawValue
	,  := asn1.Unmarshal(, &)
	if  != nil {
		return false, nil, fmt.Errorf("error unmarshalling NegotiationToken: %v", )
	}
	switch .Tag {
	case 0:
		var  marshalNegTokenInit
		_,  = asn1.Unmarshal(.Bytes, &)
		if  != nil {
			return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Init): %v", .Tag, )
		}
		 := NegTokenInit{
			MechTypes:      .MechTypes,
			ReqFlags:       .ReqFlags,
			MechTokenBytes: .MechTokenBytes,
			MechListMIC:    .MechListMIC,
		}
		return true, , nil
	case 1:
		var  marshalNegTokenResp
		_,  = asn1.Unmarshal(.Bytes, &)
		if  != nil {
			return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Resp/Targ): %v", .Tag, )
		}
		 := NegTokenResp{
			NegState:      .NegState,
			SupportedMech: .SupportedMech,
			ResponseToken: .ResponseToken,
			MechListMIC:   .MechListMIC,
		}
		return false, , nil
	default:
		return false, nil, errors.New("unknown choice type for NegotiationToken")
	}

}

// NewNegTokenInitKRB5 creates new Init negotiation token for Kerberos 5
func ( *client.Client,  messages.Ticket,  types.EncryptionKey) (NegTokenInit, error) {
	,  := NewKRB5TokenAPREQ(, , , []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{})
	if  != nil {
		return NegTokenInit{}, fmt.Errorf("error getting KRB5 token; %v", )
	}
	,  := .Marshal()
	if  != nil {
		return NegTokenInit{}, fmt.Errorf("error marshalling KRB5 token; %v", )
	}
	return NegTokenInit{
		MechTypes:      []asn1.ObjectIdentifier{gssapi.OIDKRB5.OID()},
		MechTokenBytes: ,
	}, nil
}